﻿namespace Sedodream.Web.Common.Contact
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.IO;
    using System.Linq;
    using System.Xml.Linq;
    using Sedodream.Web.Common.Exceptions;
    using Sedodream.Web.Common.Extensions;

    public class ContactManager
    {
        private static readonly ContactManager instance = new ContactManager();
        private const string XmlNamespace = "http://schemas.sedodream.com/ViewHelper/2009/05";

        private readonly XDocument mContactsDocument;
        private readonly XElement mRootElement;
        private readonly XNamespace mNamespace;

        private readonly string mContactFileFullPath;

        private object mAddLock = new object();
        private object mSaveLock = new object();

        private ContactManager()
        {
            // load up the XDoc
            // if the doc doesn't exist then create it
            string contactsXmlFilePath = ConfigurationManager.AppSettings["contactsXmlFle"];
            if (string.IsNullOrEmpty(contactsXmlFilePath))
            {
                throw new ConfigurationErrorsException(string.Format("Missing configruation value for [{0}]", "contactsXmlFle"));
            }

            this.mNamespace = XmlNamespace;

            FileInfo contactsFileInfo = new FileInfo(contactsXmlFilePath);
            this.mContactFileFullPath = contactsFileInfo.FullName;

            if (!contactsFileInfo.Exists)
            {
                // create a new file and write it out
                this.mContactsDocument = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"));
                this.mRootElement = new XElement(this.mNamespace + "Contacts");
                this.mContactsDocument.Add(this.mRootElement);
                this.mContactsDocument.Save(contactsFileInfo.FullName);
            }
            else
            {
                this.mContactsDocument = XDocument.Load(contactsFileInfo.FullName);
                this.mRootElement = this.mContactsDocument.Element(this.mNamespace + "Contacts");
            }
        }

        public static ContactManager Instance
        {
            get { return instance; }
        }

        public IEnumerable<Contact> GetAllContacts()
        {
            var contactElements = from c in this.mRootElement.Elements()
                                  select c;

            IList<Contact> allContacts = new List<Contact>();
            foreach (var contactElement in contactElements)
            {
                Contact contact = this.ConvertXElementToContact(contactElement);

                allContacts.Add(contact);
            }

            return allContacts;
        }

        public Contact GetContactById(Guid id)
        {
            Contact result = null;

            var contactQuery = from c in this.mRootElement.Elements()
                               where c.Attribute("Id").Value == id.ToString()
                               select c;

            if (contactQuery != null)
            {
                result = this.ConvertXElementToContact(contactQuery.First());
            }

            return result;
        }

        public void ValidateContact(Contact contact)
        {
            if (contact == null) { throw new ArgumentNullException("contact"); }

            ValidationException vex = new ValidationException();

            if (string.IsNullOrEmpty(contact.FirstName))
            { vex.AddValidationError("FirstName", "First name is required"); }

            if (string.IsNullOrEmpty(contact.LastName))
            { vex.AddValidationError("LastName", "Last name is required"); }

            if (string.IsNullOrEmpty(contact.Email))
            { vex.AddValidationError("Email", "Email is required"); }
            else if (!System.Text.RegularExpressions.Regex.IsMatch(
                        contact.Email,
                        @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"))
            { vex.AddValidationError("Email", "Email not in the correct format"); }

            if (string.IsNullOrEmpty(contact.Phone))
            { vex.AddValidationError("Phone", "Phone is required"); }
            else if (!System.Text.RegularExpressions.Regex.IsMatch(contact.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
            { vex.AddValidationError("Phone", "Phone number not in the correct format"); }

            if (vex.ValidationErrors.Count > 0)
            {
                throw vex;
            }
        }

        public void RemoveAllContacts()
        {
            throw new NotImplementedException();
        }

        public void AddContact(Contact contact)
        {
            if (contact == null) { throw new ArgumentNullException("contact"); }
            if (string.IsNullOrEmpty(contact.FirstName)) { throw new ArgumentNullException("contact.FirstName"); }
            if (string.IsNullOrEmpty(contact.LastName)) { throw new ArgumentNullException("contact.LastName"); }

            lock (this.mAddLock)
            {
                XElement contactElement = new XElement(
                                            this.mNamespace + "Contact",
                                                new XAttribute("Id", contact.Id.ToString()),
                                                new XAttribute("FirstName", contact.FirstName),
                                                new XAttribute("LastName", contact.LastName),
                                                new XAttribute("Email", contact.Email),
                                                new XAttribute("Phone", contact.Phone),
                                                new XAttribute("Gender", contact.Gender));
                this.mRootElement.Add(contactElement);
            }
        }

        public void UpdateContact(Contact contact)
        {
            if (contact == null) { throw new ArgumentNullException("contact"); }
            if (contact.Id.Equals(Guid.Empty)) { throw new ArgumentNullException("contact.Id"); }

            var contactElementQuery = from c in this.mRootElement.Elements()
                                      where c.Attribute("Id").Value.Equals(contact.Id.ToString())
                                      select c;

            if (contactElementQuery.Count() <= 0)
            {
                string message = string.Format("Unable to find contact with id [{0}]", contact.Id);
                throw new ArgumentException(message);
            }

            var contactElement = contactElementQuery.First();

            this.UpdateXElementFromContact(contactElement, contact);
        }

        public void SaveChanges()
        {
            lock (this.mSaveLock)
            {
                this.mContactsDocument.Save(this.mContactFileFullPath);
            }
        }

        private Contact ConvertXElementToContact(XElement contactElement)
        {
            if (contactElement == null) { throw new ArgumentNullException("contactElement"); }

            Contact contact = new Contact
            {
                Id = new Guid(contactElement.GetSafeValue("Id")),
                FirstName = contactElement.GetSafeValue("FirstName"),
                LastName = contactElement.GetSafeValue("LastName"),
                Email = contactElement.GetSafeValue("Email"),
                Phone = contactElement.GetSafeValue("Phone"),
                Gender = (contactElement.GetSafeValue("Gender") != null)
                            ? ConvertToEnum<Gender>(contactElement.GetSafeValue("Gender"))
                            : null
            };

            return contact;
        }

        private void UpdateXElementFromContact(XElement contactElement, Contact contact)
        {
            if (contactElement == null) { throw new ArgumentNullException("contactElement"); }
            if (contact == null) { throw new ArgumentNullException("contact"); }

            contactElement.GetOrCreateAttribute("Id").Value = contact.Id.ToString();
            contactElement.GetOrCreateAttribute("FirstName").Value = contact.FirstName;
            contactElement.GetOrCreateAttribute("LastName").Value = contact.LastName;
            contactElement.GetOrCreateAttribute("Email").Value = contact.Email;
            contactElement.GetOrCreateAttribute("Phone").Value = contact.Phone;
            contactElement.GetOrCreateAttribute("Gender").Value = contact.Gender.HasValue
                ? contact.Gender.ToString()
                : string.Empty;
        }

        private Nullable<T> ConvertToEnum<T>(string enumString)
            where T : struct
        {
            Nullable<T> result = null;
            if (!string.IsNullOrEmpty(enumString))
            {
                result = (T)Enum.Parse(typeof(T), enumString, true);
            }

            return result;
        }
    }
}